#include <strstream>

#include <kuai/mol/inchi.hpp>
#include <kuai/db/moldb.hpp>
#include <kuai/mol/space.hpp>
#include <kuai/mol/molio.hpp>
#include <kuai/mol/sss.hpp>

namespace kuai {

	IMPLEMENT_JOB_CREATOR(InchiJob, inchi);

	InchiJob::InchiJob() 
		: n(0), failed(0)
	{ }

	void InchiJob::pushParameter(const String& key, const String& data, std::ostream& output, std::ostream& errlog) {
		if (boost::algorithm::iequals(key, "options")) {
			options = data;
		}
		else {
			std::istringstream stream(data);
			PhaseSpace space;
			++n;
			try {
				if (MolReaderManager::getInstance().read(stream, key, space)) {
					String result[4];
					inchikey(space, const_cast<char*>(options.c_str()), result);
					output << "Structure: " << n << std::endl
					<< result[0] << std::endl
					<< result[1] << std::endl
					<< "InChIKey=" << result[3] << std::endl;
					if (!result[2].empty()) {
						dumpWarning(result[2].c_str(), errlog);
					}
					output.flush();
				}
				else {
					throw error("Failed to read mol information from %1%", key);
				}
			}
			catch (std::runtime_error& err) {
				++failed;
				dumpError(err, errlog);
			}
		}
	}

	void InchiJob::doJob(std::ostream& output, std::ostream& error) {
		output << "Finished processing " << n << " structures: " << failed << " errors." << std::endl;
	}

	JOB_HELP_FUNCTION(InchiJob, output) {
		// TODO:
	}


	namespace {
		static const char* SQL_CREATE_MOL_TABLE = 
			"CREATE TABLE IF NOT EXISTS Molecule ( "
				"id INTEGER PRIMARY KEY AUTOINCREMENT, "
				"natoms INTEGER, "
				"nbonds INTEGER, "
				"weight DOUBLE, "
				"formula CHAR(200), "
				"inchi_key CHAR(25) UNIQUE, "
				"data TEXT, "
				"sss_filter TEXT DEFAULT \'\' "
			");\n"
			"CREATE INDEX IF NOT EXISTS mol_natoms "
				"ON molecule (natoms ASC);\n"
			"CREATE INDEX IF NOT EXISTS mol_nbonds "
				"ON molecule (nbonds ASC);\n"
			"CREATE INDEX IF NOT EXISTS mol_weight "
				"ON molecule (weight ASC);\n"
			"CREATE INDEX IF NOT EXISTS mol_formula "
				"ON molecule (formula ASC);\n"
		;

		static const char* SQL_FIND_MOLID = "SELECT id FROM molecule WHERE inchi_key = ?;";
		static const char* SQL_INSERT_MOL = "INSERT INTO molecule "
			"(natoms, nbonds, weight, formula, inchi_key, data) "
			"VALUES (?, ?, ?, ?, ?, ?);";
	}

	boost::int64_t TinymolJob::saveMol(Sqlite3DatabasePointer pdb, PhaseSpace& space, std::ostream& outlog) {
		String keys[4];
		inchikey(space, "", keys);
		boost::int64_t result = 0;
		outlog << keys[3];

		try {
			Sqlite3StatementPointer pSQL = pdb->prepare(SQL_FIND_MOLID);
			pSQL->bind(1, keys[3]);
			result = pdb->getSingleInt(*pSQL);
			outlog << " existed at " << result;
		}
		catch (std::runtime_error&) {
			setupAromaticBond(space);
			std::ostringstream oss;
			KuaiMolWriter::getInstance().write(oss, space);

			Sqlite3StatementPointer pSQL = pdb->prepare(SQL_INSERT_MOL);
			String::size_type i1 = 9; // "InChI=1S/H";
			String::size_type i2 = keys[0].find(keys[0][8], i1);
			String formula;
			if (i2 == String::npos) {
				formula = keys[0].substr(i1);
			}
			else {
				formula = keys[0].substr(i1, i2-i1);
			}
			pSQL->bind(1, int(space.countAtoms()));
			pSQL->bind(2, int(space.countBonds()));
			pSQL->bind(3, formulaWeight(formula));
			pSQL->bind(4, formula);
			pSQL->bind(5, keys[3]);
			pSQL->bind(6, oss.str());
			result = pdb->insert(*pSQL);
			outlog << " inserted into " << result;
		}
		outlog << std::endl;
		return result;
	}

	IMPLEMENT_JOB_CREATOR(ImportSDFJob, import_sdf);
	

	void ImportSDFJob::doJob(Sqlite3DatabasePointer pdb, std::ostream& out_log, std::ostream& err_log, StringMap& par) {
		FileName sdfFile(getParameter(par, "source", String("")));
		std::ifstream input;
		tryOpen(input, sdfFile);

		const Char* pUselessCoordType = MOL_INFO_ATOM_COORD_3D;

		pdb->execute(SQL_CREATE_MOL_TABLE);
		PhaseSpace space;
		MDLMolReader reader;
		size_t i = 0;
		while (reader.read(input, space)) {
			space.erase(pUselessCoordType);
			if (space.countAtoms() > 0) {
				out_log << "Structure " << ++i << ":"; 
				saveMol(pdb, space, out_log);
			}
			String line;
			while (getline(input, line) && line != "$$$$") 
			{ }
		}
	}

	JOB_HELP_FUNCTION(ImportSDFJob, output) {
		output << 
			"This job import SDF file into database.\n"
			"Parameters:\n"
		;
		DatabaseJob::help(output);
		output <<
			"    SOURCE=import.sdf\n"
			"        import.sdf is the file name to be imported\n"
			"        Using embed section to assign multi files in one job\n"
		;
	}

	IMPLEMENT_JOB_CREATOR(ImportRDFJob, import_rdf);

	void ImportRDFJob::doJob(Sqlite3DatabasePointer pdb, std::ostream& out_log, std::ostream& err_log, StringMap& par) {
		FileName rdfFile(getParameter(par, "source", String("")));
		std::ifstream input;
		tryOpen(input, rdfFile);

		String line;
		size_t n = 0;
		while (getline(input, line)) {
			if (boost::algorithm::starts_with(line, "$RXN")) {
				
			}
			else if (boost::algorithm::starts_with(line, "$DATUM $MFMT")) {
				PhaseSpace space;
				MolReaderManager::getInstance().read(input, ".mol", space);
				Index nAtoms = space.countAtoms();
				if (nAtoms > 0) {
					Index nBonds = space.countBonds();
					String codes[4];
					inchikey(space, "", codes);
					out_log << codes[0] << std::endl
						<< codes[1] << std::endl
						<< codes[2] << std::endl
						<< codes[3] << std::endl
						<< std::endl << std::endl;
					++n;
				}
			}
		}
		out_log << n << std::endl;

	}

	JOB_HELP_FUNCTION(ImportRDFJob, output) {
		output << 
			"This job import RDF file into database.\n"
			"Parameters:\n"
		;
		DatabaseJob::help(output);
		output <<
			"    SOURCE=import.rdf\n"
			"        import.rdf is the file name to be imported\n"
			"        Using embed section to assign multi files in one job\n"
		;	
	}


	IMPLEMENT_JOB_CREATOR(QueryTinymolJob, query_db);

	static const char* SQL_SELECT_SSS_RECORDS = 
		"SELECT id, formula, inchi_key, sss_filter, data "
		"FROM Molecule "
		"WHERE natoms >= ? and nbonds >= ?  "
		"ORDER BY weight\n"
	;

	QueryTinymolJob::QueryTinymolJob() 
	{ }

	void QueryTinymolJob::pushParameter(const String& key, const String& data, std::ostream& output, std::ostream& errlog) 
	{
		try {
			if (key == "record") {
				boost::shared_ptr<Sqlite3Statement> sql = pdb->prepare("SELECT * FROM Molecule WHERE inchi_key = ?");
				sql->bind(1, data);
				int code = sql->step();
				switch (code) {
				case SQLITE_ROW:
					dumpMolRecord(*sql, output);
					break;

				case SQLITE_DONE:
					throw ErrorCanNotFindRow(str(*sql));
					break;

				default:
					{
						throw pdb->last_error();
					}
				}
			}
			else if (key == "sss_mol") {
				if (pdb.get() == NULL) {
					throw error("Please Open the database at first");
				}
				PhaseSpace space;
				if (!MolReaderManager::getInstance().parse(data, ".mol", space)) {
					throw error("Can not read molecule");
				}
				Index nAtoms = space.countAtoms();
				if (nAtoms == 0) {
					throw error("It is an empty molecule");
				}
				Index nBonds = space.countBonds();

				setupAromaticBond(space);

				boost::shared_ptr<Sqlite3Statement> stat = pdb->prepare(SQL_SELECT_SSS_RECORDS);
				stat->bind(1, int(nAtoms));
				stat->bind(2, int(nBonds));

				size_t count = 0;
				while (stat->step() == SQLITE_ROW) {
					PhaseSpace space2;
					if (loadMol(*stat, 4, space2)) {;
						String ss = stat->column(1);
						if (sss(space2, space)) {
							output << stat->getInt64(0) << ": " << stat->column(1) << ": " << stat->column(2) << ": " << std::endl;
							++count;
						}
					}
				}
					
				output << "Found " << count << " structure(s) with input pattern." << std::endl;
			}
			else if (key == "formula") {
				// TODO
			}
			else if (key == "database") {
				pdb = Sqlite3Database::create(FileName(data), SQLITE_READONLY);
			}
			else {
				throw error("Unknown Command %1%", key);
			}
		}
		catch (std::exception& error) {
			dumpError(error, errlog);
		}
	}

	void QueryTinymolJob::doJob(std::ostream& output, std::ostream& error) 
	{ }


	void QueryTinymolJob::dumpMolRecord(Sqlite3Statement& sql, std::ostream& output) {
		PhaseSpace space;
		loadMol(sql, 6, space);

		MDLMolWriter write;
		write.write(output, space);

		output << ">  <ID>\n" << sql.getInt64(0) 
			<< "\n\n>  <FORMULA>\n" << sql.column(4) 
			<< "\n\n>  <INCHI_KEY>\n" << sql.column(5) 
			<< "\n\n>  <WEIGHT>\n" << sql.column(3) 
			<< "\n\n$$$$\n";
	}

	bool QueryTinymolJob::loadMol(Sqlite3Statement& sql, int icol, PhaseSpace& space) {
		String s = sql.column(icol);
		std::istringstream stream(s);
		return KuaiMolReader::getInstance().read(stream, space);
	}

	JOB_HELP_FUNCTION(QueryTinymolJob, output) {
		// TODO:
	}


}
